package scalax.collection
package generic

import language.higherKinds
import annotation.unchecked.uncheckedVariance
import collection.generic.CanBuildFrom
import collection.mutable.{Builder, ListBuffer}
import scala.reflect.ClassTag

import GraphEdge.{EdgeLike, EdgeCompanionBase}
import GraphPredef.{EdgeLikeIn, Param, OuterNode}
import config.{GraphConfig, CoreConfig}
import mutable.GraphBuilder

/**
 * Methods common to `Graph` companion objects in the core module.
 * 
 * @tparam CC the kind of type of the graph that is to become the companion class/trait
 *         of the object extending this trait. 
 * @define DUPLEXCL Duplicate exclusion takes place on the basis of values
 *         returned by `hashCode` of the supplied nodes and edges. The hash-code
 *         value of an edge is determined by its ends and optionally by other
 *         edge components such as `weight` or `label`. To include non-node edge
 *         components in the hash-code of an edge make use of any of the predefined
 *         key-weighted/key-labeled edges or mix `ExtendedKey` into your custom
 *         edge class. 
 * @define EDGES all edges to be included in the edge set of the graph to be
 *         created. Edge ends will be added to the node set automatically.
 * @define INNODES The isolated (and optionally any other) outer nodes that the node set of
 *         this graph is to be populated with. This parameter may be used as an alternative
 *         or in addition to `nodeStreams`.
 * @define INEDGES The outer edges that the edge set of this graph is to be populated with.
 *         Nodes being the end of any of these edges will be added to the node set.
 *         This parameter is meant be used as an alternative or in addition to `edgeStreams`.
 * @author Peter Empen
 */
trait GraphCompanion[+CC[N, E[X] <: EdgeLikeIn[X]] <: Graph[N,E] with GraphLike[N,E,CC]]
{
  /** Type of configuration required for a specific `Graph` companion. */
  type Config <: GraphConfig
  /** The default configuration to be used in absence of a user-supplied configuration. */
  def defaultConfig: Config

  protected[this] type Coll = CC[_,Nothing]
  /** Creates an empty `Graph` instance. */
  def empty[N, E[X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]],
                                      config: Config): CC[N,E]
  /** Creates a `Graph` with a node set built from all nodes in `elems` including
   * edge ends and with an edge set containing all edges in `elems`.
   * $DUPLEXCL
   * 
   * @param   elems sequence of nodes and/or edges in an arbitrary order
   * @return  A new graph instance containing the nodes and edges derived from `elems`.
   */
  def apply[N, E[X] <: EdgeLikeIn[X]](elems: Param[N,E]*)
                                     (implicit edgeT: ClassTag[E[N]],
                                      config: Config): CC[N,E] =
    (newBuilder[N,E] ++= elems).result
  /**
   * Produces a graph with a node set containing all `nodes` and edge ends in `edges`
   * and with an edge set containing all `edges` but duplicates.
   * $DUPLEXCL
   * 
   * @param nodes the isolated and optionally any other non-isolated nodes to
   *        be included in the node set of the graph to be created.
   * @param edges $EDGES
   * @return  A new graph instance containing `nodes` and all edge ends
   *          and `edges`.
   */
  def from [N, E[X] <: EdgeLikeIn[X]](nodes: collection.Iterable[N] = Seq.empty[N],
                                      edges: collection.Iterable[E[N]])
                                     (implicit edgeT: ClassTag[E[N]],
                                      config: Config): CC[N,E]
  /**
   * Produces a graph containing the results of some element computation a number of times.
   * $DUPLEXCL
   *
   * @param   nr  the number of elements to be contained in the graph.
   * @param   elem the element computation returning nodes or edges `nr` times.
   * @return  A graph that contains the results of `nr` evaluations of `elem`.
   */
  def fill[N, E[X] <: EdgeLikeIn[X]] (nr: Int)(elem: => Param[N,E])
                                     (implicit edgeT: ClassTag[E[N]],
                                      config: Config): CC[N,E] = {
    val gB = newBuilder[N,E].asInstanceOf[GraphBuilder[N,E,CC]]
    gB.sizeHint(nr)
    var i = 0
    while (i < nr) {
      gB += elem
      i += 1
    }
    gB.result
  }
  def newBuilder[N, E[X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]],
                                           config: Config): Builder[Param[N,E], CC[N,E]] =
    new GraphBuilder[N,E,CC](this)
  class GraphCanBuildFrom[N, E[X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]],
                                                    config: Config)
    extends CanBuildFrom[Coll @uncheckedVariance, Param[N,E], CC[N,E] @uncheckedVariance]
  {
    def apply(from: Coll @uncheckedVariance) = newBuilder[N,E]
    def apply() = newBuilder[N,E]
  }
}
/** `GraphCompanion` extended to work with `CoreConfig`. */
trait GraphCoreCompanion[+CC[N, E[X] <: EdgeLikeIn[X]] <: Graph[N,E] with GraphLike[N,E,CC]]
  extends GraphCompanion[CC]
{
  type Config = CoreConfig
  def defaultConfig = CoreConfig()
  def empty[N, E[X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]],
                                      config: Config = defaultConfig): CC[N,E]
  override
  def apply[N, E[X] <: EdgeLikeIn[X]](elems: Param[N,E]*)
                                     (implicit edgeT: ClassTag[E[N]],
                                      config: Config = defaultConfig): CC[N,E] =
    super.apply(elems: _*)(edgeT, config)
  def from [N, E[X] <: EdgeLikeIn[X]](nodes: collection.Iterable[N] = Seq.empty[N],
                                      edges: collection.Iterable[E[N]])
                                     (implicit edgeT: ClassTag[E[N]],
                                      config: Config = defaultConfig): CC[N,E]
  override
  def fill[N, E[X] <: EdgeLikeIn[X]] (nr: Int)(elem: => Param[N,E])
                                     (implicit edgeT: ClassTag[E[N]],
                                      config: Config = defaultConfig): CC[N,E] =
    super.fill(nr)(elem)(edgeT, config)
}
trait ImmutableGraphCompanion[+CC[N, E[X] <: EdgeLikeIn[X]] <:
                               immutable.Graph[N,E] with GraphLike[N,E,CC]]
  extends GraphCoreCompanion[CC]

trait MutableGraphCompanion[+CC[N, E[X] <: EdgeLikeIn[X]] <:
                             mutable.Graph[N,E] with mutable.GraphLike[N,E,CC]]
  extends GraphCoreCompanion[CC]
{
  override def newBuilder[N, E[X] <: EdgeLikeIn[X]]
              (implicit edgeT: ClassTag[E[N]],
               config: Config): Builder[Param[N,E], CC[N,E] @uncheckedVariance] =
    new GraphBuilder[N,E,CC](this)(edgeT, config)
}